package com.glodon.paas.document.biz.impl;

import static com.glodon.paas.document.constant.DocumentConstant.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.glodon.paas.consts.DatabaseConst;
import com.glodon.paas.document.biz.FileBiz;
import com.glodon.paas.document.constant.DocumentConstant;
import com.glodon.paas.document.dao.FileDao;
import com.glodon.paas.document.dao.LockDao;
import com.glodon.paas.document.dao.ShareDao;
import com.glodon.paas.document.dao.domain.FileInfoEntity;
import com.glodon.paas.document.dao.domain.LockEntity;
import com.glodon.paas.document.service.util.StorageKeyNamingPolicy;
import com.glodon.paas.exception.PaasServerExceptionFactory;
import com.glodon.paas.exception.i18n.ErrorCode;
import com.glodon.paas.storage.BulkStorage;
import com.glodon.paas.util.UUIDUtil;

@Repository("fileBiz")
public class FileBizImpl implements FileBiz {

    private static final Logger LOGGER                = LoggerFactory.getLogger(FileBizImpl.class);
    Integer                     ORDER_BY_DESC_INTEGER = new Integer(DatabaseConst.ORDER_BY_DESC_INT);
    @Autowired
    private FileDao             fileDao;

    @Autowired
    private ShareDao            shareDao;

    @Autowired
    private LockDao             lockDao;

    @Autowired
    private BulkStorage         bulkStorage;

    @Override
    public FileInfoEntity getFile(String fileId) {
        if (StringUtils.isEmpty(fileId)) {
            throw PaasServerExceptionFactory.validateException("fileId is empty.", null);
        }
        return fileDao.getFile(fileId, null, null);
    }

    // @Override
    // public FileInfoEntity getFile(String userId, String fileId) {
    // if(StringUtils.isEmpty(userId)){
    // throw PaasServerExceptionFactory.validateException("userId is empty.", null);
    // }
    // FileInfoEntity file = this.getFile(fileId);
    // if(file == null){
    // throw PaasServerExceptionFactory.notFoundException("file["+fileId+"] not exist.",
    // null).setErrorCode(ErrorCode.FILE_NOT_EXIST);
    // }
    // if(!file.getOwnerId().equals(userId)){
    // throw PaasServerExceptionFactory.privilegeException("User["+userId+"] is not the owner of file["+fileId+"]",
    // null);
    // }
    // return file;
    // }

    @Override
    public FileInfoEntity getNormalFile(String fileId) {
        FileInfoEntity file = fileDao.getFile(fileId, new Integer[] { FILE_STATUS_NORMAL }, null);
        if (file == null) {
            throw PaasServerExceptionFactory.notFoundException("can not find file[" + fileId + "]", null).setErrorCode(ErrorCode.FILE_NOT_EXIST);
        }
        return file;
    }

    // @Override
    // public FileInfoEntity getNormalFile(String userId, String fileId) {
    // // if(StringUtils.isEmpty(userId)){
    // // throw PaasServerExceptionFactory.validateException("userId is empty.", null);
    // // }
    // FileInfoEntity file = this.getNormalFile(fileId);
    // if(file == null){
    // throw PaasServerExceptionFactory.notFoundException("file["+fileId+"] not exist.",
    // null).setErrorCode(ErrorCode.FILE_NOT_EXIST);
    // }
    // // if(!file.getOwnerId().equals(userId)){
    // // throw PaasServerExceptionFactory.privilegeException("User["+userId+"] is not the owner of file["+fileId+"]",
    // null);
    // // }
    // return file;
    // }

    // @Override
    // public List<FileInfoEntity> getAvailableFiles(String fileId) {
    // if(StringUtils.isEmpty(fileId)){
    // throw PaasServerExceptionFactory.validateException("fileId is empty.", null);
    // }
    //
    // List<FileInfoEntity> files = fileDao.getFiles(fileId, null, new Integer[]{FILE_STATUS_NORMAL}, null);
    // // if(files != null){
    // // for(int index =0;index<files.size();index++){
    // // FileInfoEntity file = files.get(index);
    // // if(!file.isFolder() && file.getLatestAvailableRevisionNum() <= 0 ){
    // // files.remove(index--);
    // // }
    // // }
    // // }
    // return files;
    // }

    // @Override
    // public boolean isFileOwner(String userId, String fileId) {
    // if(StringUtils.isEmpty(userId)){
    // throw PaasServerExceptionFactory.validateException("userId is empty.", null);
    // }
    // if(userId.equals(fileId)){
    // return true;
    // }
    // else{
    // return userId.equals(fileDao.getFileOwner(fileId));
    // }
    // }

    @Override
    public void insertFile(FileInfoEntity file) {
        if (file == null) {
            throw PaasServerExceptionFactory.validateException("file object is empty.", null);
        }
        if (file.getExtension() == null) {
            file.setExtension("");
        }
        if ((file.isFolder() && this.isFolderExist(file.getParentId(), file.getFullName()))
            || (!file.isFolder() && this.isDocExist(file.getParentId(), file.getFullName()))) {
            throw PaasServerExceptionFactory.existException("exist the same file[" + file.getFullName() + "]", null);
        }
        if (StringUtils.isEmpty(file.getId())) {
            file.setId(UUIDUtil.getUUIDStr());
            fileDao.insert(file);
        } else {
            fileDao.delete(file.getId());
            fileDao.insert(file);
        }

        if (!file.isRootFile()) {
            fileDao.refreshCount(file.getParentId());
        }
    }

    private void updateStatus(FileInfoEntity file, Integer status) {
        if (file.isFolder()) {
            List<FileInfoEntity> files = fileDao.getFiles(file.getId(), null, new Integer[] { FILE_STATUS_NORMAL, FILE_STATUS_RECYCLE, FILE_STATUS_RECYCLE_CASCADE }, null);
            if (files != null) {
                for (FileInfoEntity f : files) {
                    // restore or delete
                    if (FILE_STATUS_NORMAL.intValue() == status.intValue() || FILE_STATUS_DELETED.intValue() == status.intValue()) {
                        updateStatus(f, status);
                    }
                    // delete to recycler
                    else if (FILE_STATUS_RECYCLE_CASCADE.intValue() == status.intValue() || FILE_STATUS_RECYCLE.intValue() == status.intValue()){
                            if (f.isDeleted()) {
                                continue;
                            } else {
                                updateStatus(f, status);
                            }
                    }
                }
            }
        }
        //移除 共享信息
        if (file.getShareType() != DocumentConstant.SHARE_TYPE_NORMAL && (FILE_STATUS_RECYCLE_CASCADE.intValue() == status.intValue() || FILE_STATUS_RECYCLE.intValue() == status.intValue())) {
            shareDao.deleteByFile(file.getId(), null);
            fileDao.updateShareType(file.getId(), DocumentConstant.SHARE_TYPE_NORMAL);
        }
        fileDao.updateStatus(file.getId(), status);
    }

    @Override
    public long getUsedSpace(String userId) {
        if (StringUtils.isEmpty(userId)) {
            throw PaasServerExceptionFactory.validateException("userId is empty.", null);
        }
        return fileDao.getUsedSpace(userId);
    }

    @Override
    public void updateFileName(String fileId, String newName, String updator) {
        fileDao.updateFileName(fileId, newName, updator);
    }

    @Override
    public boolean isFolderExist(String parentId, String folderName) {
        List<FileInfoEntity> files = fileDao.getFiles(parentId, folderName, new Integer[] { FILE_STATUS_NORMAL },
                                                      new Integer[] { FILE_TYPE_FOLDER });
        if (files != null && files.size() > 0) {
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean isDocExist(String parentId, String fullName) {
        List<FileInfoEntity> files = fileDao.getFiles(parentId, fullName, new Integer[] { FILE_STATUS_NORMAL },
                                                      new Integer[] { FILE_TYPE_FILE });
        if (files != null && files.size() > 0) {
            return true;
        } else {
            return false;
        }
    }

    // @Override
    // public FileInfoEntity getDeletedFile(String userId, String fileId) {
    // FileInfoEntity file = this.getFile(userId, fileId);
    // if(file.getDeletedStatus().intValue() != DocumentConstant.FILE_STATUS_RECYCLE){
    // throw PaasServerExceptionFactory.notFoundException("deleted file["+fileId+"] not exist.",
    // null).setErrorCode(ErrorCode.FILE_NOT_EXIST);
    // }
    // return file;
    // }

    @Override
    public void removeFile(String fileId) {
        FileInfoEntity file = this.getNormalFile(fileId);
        this.updateStatus(file, FILE_STATUS_RECYCLE_CASCADE);
        fileDao.updateStatus(file.getId(), FILE_STATUS_RECYCLE);
        if (!file.isRootFile()) {
            fileDao.refreshCount(file.getParentId());
        }
    }

    @Override
    public void deleteFile(String fileId) {
        FileInfoEntity file = this.getFile(fileId);
        if (file != null) {
            this.updateStatus(file, FILE_STATUS_DELETED);
            if (!file.isRootFile()) {
                fileDao.refreshCount(file.getParentId());
            }
        }
    }

    @Override
    public List<String> restore(String fileId) {
        FileInfoEntity file = this.getDeletedFile(fileId);
        List<String> failedFile = new ArrayList<String>();
        if (file != null) {
            restoreFile(file, buildPath(file), failedFile);
        }
        return failedFile;
    }

    @Override
    public String getFileId(String userId, String path, Integer[] status) {
        if (StringUtils.isEmpty(userId)) {
            throw PaasServerExceptionFactory.validateException("userId is empty.", null);
        }
        if (StringUtils.isEmpty(path)) {
            throw PaasServerExceptionFactory.validateException("path is empty.", null);
        }
        String id = fileDao.getFileId(userId, path, status, null);
        if (StringUtils.isEmpty(id)) {
            throw PaasServerExceptionFactory.notFoundException("can not find the file[path:" + path + "]", null).setErrorCode(ErrorCode.FILE_NOT_EXIST);
        } else {
            return id;
        }
    }

    @Override
    public List<FileInfoEntity> getParents(FileInfoEntity file) {
        List<FileInfoEntity> files = new ArrayList<FileInfoEntity>();
        while (file != null) {
            file = fileDao.get(file.getParentId());
            if (file != null) {
                files.add(file);
            }
        }
        return files;
    }

    // /**
    // * 文件查询
    // */
    // @Override
    // public List<FileInfoEntity> getFileByParentId(
    // String ownerId,
    // String parentId,
    // Integer type,
    // Integer deletedStatus,
    // String orderBy,
    // Integer orderType,
    // Integer startIndex,
    // Integer count) {
    // if(StringUtils.isEmpty(ownerId) && StringUtils.isEmpty(parentId) ){
    // throw PaasServerExceptionFactory.validateException("userId is empty.", null);
    // }
    //
    // // // 检查排序字段
    // // if(StringUtils.isNotBlank(orderBy)){
    // // OrderUtilsFileEntity.checkOrderBy(orderBy, true);
    // // }
    //
    // // 分页参数检查
    // // if(startIndex == null){
    // // startIndex = 0;
    // // }
    // // if(count == null){
    // // count = DatabaseConst.DEFAULT_LIMIT_S;
    // // }
    //
    // List<FileInfoEntity> files;
    // // if(count < 100){
    // // // 数据库排序
    // // files = fileDao.selectByComplex(ownerId, parentId, type, deletedStatus, orderBy, orderType, startIndex,
    // count);
    // // }else{
    // // 目前不考虑翻页，有需求在考虑，排序只考虑内存排序。
    // // files = fileDao.selectByComplex(ownerId, parentId, type, deletedStatus, null, null, null, null);
    // // OrderUtilsFileEntity.order(files, orderBy, orderType);
    // // }
    // return files;
    // }

    // @Override
    // public long getSize(String fileId) {
    // FileInfoEntity file = fileDao.get(fileId);
    // if(file == null || file.getDeletedStatus() != FILE_STATUS_NORMAL){
    // return 0;
    // }else if(file.isFolder()){
    // long size = 0;
    // List<FileInfoEntity> files = fileDao.selectByComplex(null, fileId, null, FILE_STATUS_NORMAL, null, null, 0,
    // 2000);
    // if(files.size() == 2000){
    // LOGGER.warn("too many files in Folder["+fileId+"]");
    // }
    // if(files != null && files.size()>0){
    // for(FileInfoEntity f : files){
    // if(f.getLatestAvailableRevisionNum() > 0){
    // size +=getSize(f.getId());
    // }
    // }
    // }
    // return size;
    // }else{
    // return file.getSize();
    // }
    // }

    @Override
    public List<FileInfoEntity> getDeletedFiles(String userId) {
        if (StringUtils.isEmpty(userId)) {
            throw PaasServerExceptionFactory.validateException("userId is empty.", null);
        }
        return fileDao.getDeletedFiles(userId);
    }

    @Override
    public List<FileInfoEntity> getExpiredFiles(String ownerId, int nDays, int limited) {
        return fileDao.getExpiredFiles(ownerId, nDays, limited);
    }

    @Override
    public FileInfoEntity getDeletedFile(String fileId) {
        if (StringUtils.isEmpty(fileId)) {
            throw PaasServerExceptionFactory.validateException("fileId is empty.", null);
        }

        return fileDao.getFile(fileId, new Integer[] { FILE_STATUS_RECYCLE, FILE_STATUS_RECYCLE_CASCADE }, null);
    }

    @Override
    public FileInfoEntity getFile(String parentId, String name, Integer[] status, Integer[] types) {
        if (StringUtils.isEmpty(parentId) || StringUtils.isEmpty(name)) {
            throw PaasServerExceptionFactory.validateException("fileId or name is empty.", null);
        }
        List<FileInfoEntity> files = fileDao.getFiles(parentId, name, status, types);
        if (files != null && files.size() > 0) {
            return files.get(0);
        } else {
            return null;
        }
    }

    @Override
    public List<FileInfoEntity> getFiles(String parentId, String name, Integer[] status, Integer[] types) {
        if (StringUtils.isEmpty(parentId)) {
            throw PaasServerExceptionFactory.validateException("fileId is empty.", null);
        }
        return fileDao.getFiles(parentId, name, status, types);
    }

    @Override
    public void deleteExpiredFile(FileInfoEntity file) {
        if (!file.isFolder()) {
            bulkStorage.delete(StorageKeyNamingPolicy.SEED_ROOT + "/" + file.getId());
            LOGGER.debug("delete file content, key:" + StorageKeyNamingPolicy.SEED_ROOT + "/" + file.getId());
        }
        fileDao.delete(file.getId());
    }

    @Override
    public void updateParent(FileInfoEntity file, String parentId) {
        fileDao.updateParent(file.getId(), parentId);
        fileDao.refreshCount(file.getParentId());
        fileDao.refreshCount(parentId);
        file.setParentId(parentId);
    }

    private FileInfoEntity copyFolder(FileInfoEntity oldFolder, String parentId) {
        FileInfoEntity folder = new FileInfoEntity();
        folder.setId(UUIDUtil.getUUIDStr());
        folder.setName(oldFolder.getName());
        folder.setExtension("");
        folder.setType(DocumentConstant.FILE_TYPE_FOLDER);
        folder.setCreateTime(oldFolder.getCreateTime());
        folder.setUpdateTime(oldFolder.getUpdateTime());
        folder.setOwnerId(oldFolder.getOwnerId());
        folder.setCreator(oldFolder.getCreator());
        folder.setUpdator(oldFolder.getUpdator());
        if (parentId == null) {
            folder.setParentId(oldFolder.getParentId());
        } else {
            folder.setParentId(parentId);
        }
        folder.setDeletedStatus(DocumentConstant.FILE_STATUS_NORMAL);
        this.insertFile(folder);
        return folder;
    }

    private String buildPath(FileInfoEntity file) {
        List<FileInfoEntity> parents = this.getParents(file);
        String parentId = null;
        for (int i = parents.size() - 1; i >= 0; i--) {
            FileInfoEntity curFolder = parents.get(i);
            if (curFolder.isDeleted()) {
                FileInfoEntity newfolder = this.getFile(parentId == null ? curFolder.getParentId() : parentId,
                                                        curFolder.getName(),
                                                        new Integer[] { DocumentConstant.FILE_STATUS_NORMAL },
                                                        new Integer[] { DocumentConstant.FILE_TYPE_FOLDER });
                if (newfolder == null) {
                    newfolder = copyFolder(curFolder, parentId);
                }
                parentId = newfolder.getId();
            }
        }
        if (parentId == null) {
            parentId = file.getParentId();
        }

        return parentId;
    }

    private boolean restoreFile(FileInfoEntity curFile, String parentId, List<String> failedFile) {
        FileInfoEntity existSameFile = null;
        boolean del = true;
        if (curFile.isFolder()) {
            existSameFile = this.getFile(parentId, curFile.getName(),
                                         new Integer[] { DocumentConstant.FILE_STATUS_NORMAL },
                                         new Integer[] { DocumentConstant.FILE_TYPE_FOLDER });
        } else {
            existSameFile = this.getFile(parentId, curFile.getFullName(),
                                         new Integer[] { DocumentConstant.FILE_STATUS_NORMAL },
                                         new Integer[] { DocumentConstant.FILE_TYPE_FILE });
        }
        if (existSameFile == null) {
            if (!curFile.getParentId().equals(parentId)) {
                fileDao.updateParent(curFile.getId(), parentId);
            }
            this.updateStatus(curFile, DocumentConstant.FILE_STATUS_NORMAL);
            if (!curFile.isRootFile()) {
                fileDao.refreshCount(parentId);
            }
        } else if (curFile.isFolder()) {
            parentId = existSameFile.getId();
            List<FileInfoEntity> files = fileDao.getFiles(curFile.getId(),
                                                          null,
                                                          new Integer[] { DocumentConstant.FILE_STATUS_RECYCLE_CASCADE },
                                                          null);
            for (FileInfoEntity file : files) {
                del = restoreFile(file, parentId, failedFile) && del;
            }
            if (del) {
                fileDao.updateStatus(curFile.getId(), DocumentConstant.FILE_STATUS_DELETED);
            }
        } else {
            failedFile.add(getPath(curFile));
            del = false;
        }
        return del;
    }

    // public void refreshCount(String fileId) {
    // FileInfoEntity file = fileDao.get(fileId);
    // if(file != null){
    // if(file.isFolder()){
    // fileDao.refreshCount(fileId);
    // }
    // else{
    // revisionDao.getRevisionsByFile(fileId).size();
    // }
    // }
    // }

    @Override
    public String getPath(FileInfoEntity file) {
        List<FileInfoEntity> parents = this.getParents(file);
        StringBuffer path = new StringBuffer("/");
        for (int i = parents.size() - 1; i >= 0; i--) {
            path.append(parents.get(i).getName()).append("/");
        }
        path.append(file.getFullName());
        return path.toString();
    }

    @Override
    public FileInfoEntity shareFile(String fileId) {
        FileInfoEntity file = this.getNormalFile(fileId);
        if (!file.isShared()) {
            if (file.isPublished()) {
                fileDao.updateShareType(file.getId(), DocumentConstant.SHARE_TYPE_SHARED_AND_PUBLISHED);
                file.setShareType(DocumentConstant.SHARE_TYPE_SHARED_AND_PUBLISHED);
            } else {
                fileDao.updateShareType(file.getId(), DocumentConstant.SHARE_TYPE_SHARED);
                file.setShareType(DocumentConstant.SHARE_TYPE_SHARED);
            }
        }
        return file;
    }

    @Override
    public void cancelShare(String fileId) {
        FileInfoEntity file = this.getNormalFile(fileId);
        if (file.isShared()) {
            if (file.isPublished()) {
                fileDao.updateShareType(fileId, DocumentConstant.SHARE_TYPE_PUBLISHED);
            } else {
                fileDao.updateShareType(fileId, DocumentConstant.SHARE_TYPE_NORMAL);
            }
        }

    }

    @Override
    public void publishFile(String fileId) {
        FileInfoEntity file = this.getNormalFile(fileId);
        if (!file.isPublished()) {
            if (file.isShared()) {
                fileDao.updateShareType(fileId, DocumentConstant.SHARE_TYPE_SHARED_AND_PUBLISHED);
            } else {
                fileDao.updateShareType(fileId, DocumentConstant.SHARE_TYPE_PUBLISHED);
            }
        }

    }

    @Override
    public void cancelPublish(String fileId) {
        FileInfoEntity file = this.getNormalFile(fileId);
        if (file.isPublished()) {
            if (file.isShared()) {
                fileDao.updateShareType(fileId, DocumentConstant.SHARE_TYPE_SHARED);
            } else {
                fileDao.updateShareType(fileId, DocumentConstant.SHARE_TYPE_NORMAL);
            }
        }
    }

    @Override
    public LockEntity lockFile(String userId, String fileId) {
        LockEntity lock = new LockEntity();
        lock.setId(UUIDUtil.getUUIDStr());
        lock.setLockType(DocumentConstant.LOCK_TYPE_WRITE);
        lock.setOwnerId(userId);
        lock.setUpdateTime(new Date());
        lockDao.insert(lock);
        fileDao.updateLock(fileId, lock.getId());
        return lock;
    }

    @Override
    public void unlockFile(String lockId) {
        lockDao.delete(lockId);
    }

    @Override
    public LockEntity getLock(String lockId) {
        return lockDao.get(lockId);
    }

    @Override
    public void updateUpdator(String fileId, String userId) {
        fileDao.updateUpdator(fileId, userId);
    }

    @Override
    public FileInfoEntity getFileWithFolderCount(String fileId) {
        return fileDao.getFileWithFolderCount(fileId);
    }
    
    public void setFileDao(FileDao fileDao) {
        this.fileDao = fileDao;
    }
    
    public void setBulkStorage(BulkStorage bulkStorage) {
        this.bulkStorage = bulkStorage;
    }
    
    public void setShareDao(ShareDao shareDao) {
        this.shareDao = shareDao;
    }
    
    public void setLockDao(LockDao lockDao) {
        this.lockDao = lockDao;
    }
}
